import java.util.GregorianCalendar;

/** Port Class
    CS158A Spring 2010 Project #2
    @author Clare Lanam & Loc Phan
    */

public class Port
{
   /** bridge to which this port is connected */
   private Bridge bridge;
   /** link to which this port sends messages */
   private Link link;
   /** current role of this port */
   private PortRole role;
   /** current state of this port */
   private PortState state;
   /** distance to root */
   private int pathCost;
   /** time in milliseconds that last bdpu was received */
   private long time_lastBPDUReceived;
   /** time in milliseconds that current state was entered */
   private long time_currentStateEntered;
   
   // ------[CONSTRUCTOR]------ //
   
   /** Default constructor starts all ports in forwarding state.
       @param bridge the bridge to which this port is attached
       */
   
   public Port(Bridge bridge)
   {
      this.bridge = bridge;
      time_lastBPDUReceived = (new GregorianCalendar()).getTimeInMillis();
      resetToDesignated();
   }
   
   // ------[ACCESSORS]------ //
   
   public Bridge getBridge() { return bridge; }
   public long getTimeLastBPDUReceived() { return time_lastBPDUReceived; }
   public long getTimeCurrentStateEntere() { return time_currentStateEntered; }
   public Link getLink() { return link; }
   public int getLinkCost() { return link.getPathCost(); }
   public int getPathCost() { if (bridge.isRoot()) return 0; return pathCost; }
   public PortRole getRole() { return role; }
   public PortState getState() { return state; }
   
   // ------[MUTATORS]------ //
   
   public void setLink(Link link) { this.link = link; }
   public void setPathCost(int pathCost) { this.pathCost = pathCost; }
   
   // ------[PUBLIC METHODS]------ //
   
   public Bridge getDistalBridge()
   {
      if (link == null)
         return null;
      return link.getDistalBridge(this);
   }
   
   public Port getDistalPort()
   {
      if (link == null)
         return null;
      return link.getDistalPort(this);
   }
   
   public void receive(BPDU bpdu)
   {
      if (bpdu == null)
         return;
      
      // found a bpdu! yay!
      time_lastBPDUReceived = (new GregorianCalendar()).getTimeInMillis();
      bridge.receive(this, bpdu);
   }
   
   /** What to do when starting a new connection or if the
       current connection has timed out */
   
   public void resetToDesignated()
   {
      state = PortState.FORWARDING;
      role = PortRole.DESIGNATED;
      pathCost = Integer.MAX_VALUE;
   }
   
   public void send(BPDU bpdu)
   {
      if (state == PortState.LEARNING)
      {
         if (((new GregorianCalendar()).getTimeInMillis()
               - Bridge.FORWARD_DELAY) > time_currentStateEntered)
            setState(PortState.FORWARDING);
      }
      
      if (state != PortState.FORWARDING || role != PortRole.DESIGNATED)
         return; // nice try :p
      
      if (link != null && bpdu != null)
         link.send(this, bpdu);
   }
   
   public void send(BPDU bpdu, boolean override) // override = send anyway
   {
      if (override && link != null && bpdu != null)
         link.send(this, bpdu);
      else send(bpdu);
   }
   
   public void setRole(PortRole role)
   {
      if (this.role == role)
         return;
      
      this.role = role;
      
      if (role == PortRole.ALTERNATE || role == PortRole.BACKUP)
         state = PortState.DISCARDING;
      else if (state == PortState.DISCARDING)
         state = PortState.LEARNING;
      
      time_currentStateEntered = (new GregorianCalendar()).getTimeInMillis(); 
   }
   
   public void setState(PortState state)
   {
      if (this.state == state)
         return;
      
      this.state = state;
      
      if (state == PortState.FORWARDING || state == PortState.LEARNING)
      {
         if (role == PortRole.ALTERNATE) // flip on
            role = PortRole.ROOT;
         else if (role == PortRole.BACKUP)
            role = PortRole.DESIGNATED;
      }
      else if (state == PortState.DISCARDING)
      {
         if (role == PortRole.ROOT) // flip off
            role = PortRole.ALTERNATE;
         else if (role == PortRole.DESIGNATED)
            role = PortRole.BACKUP;
      }
      
      time_currentStateEntered = (new GregorianCalendar()).getTimeInMillis();
   }
}
